package com.javawork.juc.four;

/**
 * JMM（Java内存模型Java Memory Model，简称JMM）本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范，
 * 通过这组规范定义了程序中（尤其是多线程）各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见，
 * 关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
 * 原则：JMM的关键技术点都是围绕多线程的原子性、可见性和有序性展开的
 * 能干嘛？
 * 1通过JMM来实现线程和主内存之间的抽象关系。
 * 2屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
 *
 * @author zhangxiaolu
 * @since 2023-02-11 14:54
 */
public class JMMDemo {

    public static void main(String[] args) {
        //1.可见性：当一个线程修改了某个共享变量的值，其他的线程是否能狗立即知道该变更，JMM规定了所有的变量都存储在主内存中。
        // 系统主内存共享变量数据修改被写入的时机是不确定的，多线程并发下很可能出现“脏读”，所以每个线程都有自己的工作内存，
        // 线程自己的工作内存中保存了该线程使用到的变量的生内存副本拷贝，线程对变量的所有操作（读取，赋值等）都必需在线程自己的工作内存
        // 中进行，而不能够直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量，线程间变量值的传递均需要通过主内存来完成

        //2.有序性：对于一个线程的执行代码而言，我们总是习惯性认为代码的执行总是从上到下，有序执行。但为了提升性能，编译器和处理器通常会对
        // 指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义，即只要程序的最终结果与它顺序化执行的结果相等，那么指令的执行顺序可
        // 以与代码顺序不一致，此过程叫指令的重排序。优缺点：JVM能根据处理器特性（CPU多级缓存系统、多核处理器等）适当的对机器指令进行重排序，
        // 使机器指令能更符合CPU的执行特性，最大限度的发挥机器性能。但是，指令重排可以保证串行语义一致，但没有义务保证多线程间的语义也一致
        // （即可能产生“脏读”），简单说，两行以上不相干的代码在执行的时候有可能先执行的不是第一条，不见得是从上到下顺序执行，执行顺序会被优化。
        // 源代码 → 编译器优化的重排 → 指令并行的重排 → 内存系统的重排 → 最终执行的指令
        // 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
        // 处理器在进行重排序时必须要考虑指令之间的数据依赖性。多线程环境中线程交替执行，由于编译器优化重排的存在，两个线程中使用的变量能否保证一致性是无法确定的，结果无法预测。

        //多线程对变量的读写过程：由于JVM运行程序的实体是线程，而每个线程创建时JVM都会为其创建一个工作内存（有些地方称为栈空间），工作内存是每个线程的私有数据区域，
        // 而Java内存模型中规定所有变量都存储在主内存，主内存是共享内存区域，所有线程都可以访问，但线程对变量的操作（读取赋值等）必须在工作内存中进行，
        // 首先要将变量从主内存拷贝到的线程自己的工作内存空间，然后对变量进行操作，操作完成后再将变量写回主内存，不能直接操作主内存中的变量，
        // 各个线程中的工作内存中存储着主内存中的变量副本拷贝，因此不同的线程间无法访问对方的工作内存，线程间的通信（传值）必须通过主内存来完成

        //在JMM中，如果一个操作执行的结果需要对另一个操作可见性或者代码重排序，那么这两个操作之间必须存在happens-before（先行发生）原则。逻辑上的先后关系
        //如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成，那么有很多操作都将会变得非常啰嗦，但是我们在编写Java并发代码的时候并没有察觉到这一点。
        //我们没有时时、处处、次次，添加volatile和synchronized来完成程序，这是因为Java语言中JMM原则下有一个“先行发生”（Happens-Before）的原则限制和规矩，给你立好了规矩。
        //它是判断数据是否存在竞争，线程是否安全的非常有用的手段。依赖这个原则，我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题，而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。

        //happens-before总原则：如果一个操作happens-before另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。
        //两个操作之间存在happens-before关系，并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致，那么这种重排序并不非法。

        //happens-before之8条：
        // 1.次序规则：一个线程内，按照代码顺序，写在前面的操作先行发生于写在后面的操作；
        // 2.锁定规则：一个unLock操作先行发生于后面（这里的“后面”是指时间上的先后）对同一个锁的lock操作；
        // 3.volatile变量规则：|对一个volatile变量的写操作先行发生于后面对这个变量的读操作，前面的写对后面的读是可见的，这里的“后面”响样是指时间上的先后。
        // 4.传递规则：如果操作A先行发生于操作B，而操作B又先行发生于操作C，则可以得出操作A先行发生于操作C；
        // 5.线程启动规则（Thread Start Rule）：Thread对象的start（）方法先行发生于此线程的每一个动作
        // 6.线程中断规则（Thread Interruption Rule）：对线程interrupt（）方法的调用先行发生于被中断线程的代码检测到中断事件的发生；
        //                                         可以通过Thread.interrupted（）检测到是否发生中断
        //                                         也就是说你要先调用interrupt（）方法设置过中断标志位，我才能检测到中断发送
        // 7.线程终止规则（Thread Termination Rule）：线程中的所有操作都先行发生于对此线程的终止检测，我们可以通过isAlive（）等手段检测线程是否已经终止执行。
        // 8.对象终结规则（Finalizer Rule）：一个对象的初始化完成（构造函数执行结束）先行发生于它的finalize（）方法的开始，即：对象没有完成初始化之前，是不能调用finalized()方法的，

    }
}

class TestDemo {

    /**
     * 使用：把value定义为volatile变量，由子setter方法value的修改不纸value的原值，满足volatile光健字使用场景
     * 理由：利volatile保证读取操作的可见性；利用synchronized保证复合操作的原子性结合使用敛和volatile 变量来减少同步的开销
     */
    private volatile int value = 0;

    public int getValue() {
        return value; //利用volatile保证读取操作的可见性
    }

    public synchronized int setValue() {
        return ++value; //利用synchronized保证复合操作的原子性
    }
}